'''
Created on Apr 29, 2013

@author: hezhuobin
'''
import random
import struct
import socket
import sys
import thread
import time
import traceback


DNS_TYPE_A = 1
DNS_CLASS_IN = 1

prime_num = (3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97)


def log(log_str):
    now = int(time.time())
    print "[%03d] %2d:%02d:%02d %s" % (thread.get_ident() % 1000,
                                     (now / 3600 + 8) % 24, now / 60 % 60, now % 60,
                                     log_str)
    sys.stdout.flush()


def encrypt(data):
    s1 = random.choice(prime_num)
    s2 = len(data) - s1
    res = struct.pack('>hh', s1, s2)
    res += ''.join(chr((ord(data[i]) + i * s1 + s2) % 256) for i in xrange(len(data)))
    if len(res) % 16:
        res += ''.join(chr(random.randint(0, 255)) for i in xrange(-len(res) % 16))
    return res


def decrypt(read_buf):
    thead = 4
    buf_len = len(read_buf)
    s1, s2 = struct.unpack('>hh', read_buf[thead - 4:thead])
    pack_len = s1 + s2

    tend = thead + pack_len
    data = ''.join((chr((ord(read_buf[thead + i]) - i * s1 - s2) % 256) for i in xrange(0, pack_len)))
    return data


class Struct(object):
    def __init__(self, **kwargs):
        for name, value in kwargs.items():
            setattr(self, name, value)


class StringIO:
    def __init__(self, data):
        self.index = 0
        self.data = data

    def read(self, size):
        ret = self.data[self.index:self.index + size]
        self.index += size
        return ret

    def seek(self, pos):
        self.index = pos

    def tell(self):
        return self.index

    def getvalue(self):
        return self.data


def parse_dns_message(data):
    message = StringIO(data)
    qid = struct.unpack('!H', message.read(2))[0]
    tmp = struct.unpack('!H', message.read(2))[0]
    qr = (tmp >> 15) & 1
    op_code = (tmp >> 11) & 0b1111
    aa = (tmp >> 10) & 1
    tc = (tmp >> 9) & 1
    rd = (tmp >> 8) & 1
    ra = (tmp >> 7) & 1
    z = (tmp >> 4) & 0b111
    rcode = tmp & 0b1111

    c_qd, c_an, c_ns, c_ar = struct.unpack('!4H', message.read(8))
    questions = []
    for i in range(c_qd):
        questions.append(parse_dns_question(message))
    records = []
    for i in range(c_an + c_ns + c_ar):
        records.append(parse_dns_record(message))
    return Struct(qr=qr, op_code=op_code, aa=aa, tc=tc, rd=rd, ra=ra,
                  z=z, rcode=rcode,
                  qid=qid, qd=c_qd, an=c_an, ns=c_ns, ar=c_ar,
                  questions=questions, records=records)


def parse_dns_question(message):
    qname = parse_domain_name(message)
    qtype, qclass = struct.unpack('!HH', message.read(4))
    end_offset = message.tell()
    return Struct(name=qname, type_=qtype, class_=qclass, end_offset=end_offset)


def parse_dns_record(message):
    name = parse_domain_name(message)
    qtype, qclass = struct.unpack('!HH', message.read(4))
    ttl_offset = message.tell()
    ttl = struct.unpack('!I', message.read(4))[0]
    rd_len = struct.unpack('!H', message.read(2))[0]
    result = message.read(rd_len)
    return Struct(name=name, type_=qtype, class_=qclass, ttl_offset=ttl_offset, ttl=ttl, result=result)


def _parse_domain_labels(message):
    labels = []
    len = ord(message.read(1))
    while len > 0:
        if len >= 64:
            len = len & 0x3f
            offset = (len << 8) + ord(message.read(1))
            mesg = StringIO(message.getvalue())
            mesg.seek(offset)
            labels.extend(_parse_domain_labels(mesg))
            return labels
        else:
            labels.append(message.read(len))
            len = ord(message.read(1))
    return labels


def parse_domain_name(message):
    return '.'.join(_parse_domain_labels(message))


def addr_p2n(addr):
    return socket.inet_aton(addr)


ip_range = []


def ip_str_to_int(ip_str):
    ip_segs = ip_str.split('.')
    ip_int = 0l
    i = 24
    for ip in ip_segs:
        ip = int(ip)
        ip_int = ip_int | (ip << i)
        i -= 8
    return ip_int


def get_mask(bits):
    return 0xffffffffL ^ (1L << (32 - bits)) - 1


def is_ip_abroad(ip_num):
    global ip_range
    low = 0
    high = len(ip_range) - 1

    while low <= high:
        mid = (low + high) // 2
        mid_ip, mid_mask = ip_range[mid]
        if mid_ip < ip_num:
            low = mid + 1
        elif mid_ip > ip_num:
            high = mid - 1
        else:
            return True
    if mid_ip > ip_num:
        if mid == 0:
            return False
        mid_ip, mid_mask = ip_range[mid - 1]
    if (mid_ip ^ ip_num) & get_mask(mid_mask):
        return False
    return True


def is_reply_available(resp):
    if resp.an and resp.tc == 0:
        for r in resp.records[:resp.an]:
            if (r.type_ == DNS_TYPE_A) and (r.class_ == DNS_CLASS_IN):
                result = r.result
                ip_num = 0L
                for i in xrange(0, 4):
                    ip_num |= ord(result[i]) << (24 - i * 8)
                return not is_ip_abroad(ip_num)
        return True
    return False


try:
    with open('routes.conf') as f:
        while True:
            s = f.readline()
            if not s:
                break
            s = s.strip()
            if not s:
                continue
            if s.startswith("#"):
                continue
            s = s.split('/')
            if len(s) == 1:
                ip_range.append((ip_str_to_int(s[0]), 32))
            elif len(s) == 2:
                ip_range.append((ip_str_to_int(s[0]), int(s[1])))
    log("Routes loaded")
except:
    log("Error loading route conf")
ip_range = sorted(ip_range, key=lambda ip: ip[0])


def bind_udp(port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(("0.0.0.0", port))
    return sock


def listen_udp(sock, callback, process_lock):
    while True:
        try:
            data, addr = sock.recvfrom(65535)
#             log("Recv data from %r" % (addr,))
        except:
            log("Error recv data")
            break
        process_lock.acquire(True)
        try:
            callback(addr, data)
        except:
            log("Error inv callback:" + traceback.format_exc())
        finally:
            process_lock.release()


if __name__ == '__main__':
    print is_ip_abroad(ip_str_to_int('123.103.14.196'))
